const fs = require('fs')
const path = require('path')
const shell = require('shelljs')
const format = require('util').format
const BMP24 = require('gd-bmp').BMP24
const Koa = require('koa')
const Router = require('koa-router')
const config = require('./config')
const Repository = require('./repository')
const LogFetcher = require('./log-fetcher')
const utils = require('./utils')

const CONTRACT_ID = config.contractId
const DEFAULT_FETCH_LIMIT = 200
const LATEST_LOG_LIMIT = 100
const CANVAS_WIDTH = 1000
const CANVAS_HEIGHT = 1000
const BLACK_COLOR = 0xffffff
const BG_IMG_BMP = path.join(config.dataDir, 'bg.bmp')
const BG_IMG_PNG = path.join(config.dataDir, 'bg.png')
const GAME_INFO_FILE = path.join(config.dataDir, 'gameinfo.json')
const RANKING_LIMIT = config.rankingLimit
const REFRESH_INTERVAL_MS = config.backgroundRefreshInterval

// global variables
let tronWeb
let contract

let cachePixels = new Map
let lastRefreshTime = 0
let gameConfigure
let gameInfo
let repo
let logFetcher

function toAddress(addr) {
  return tronWeb.address.fromHex('41' + addr.slice(2))
}

function toTrx(sun) {
  return tronWeb.fromSun(sun)
}

function createBackgroundImage(pixels) {
  const img = new BMP24(CANVAS_WIDTH, CANVAS_HEIGHT)
  img.fillRect(0, 0, CANVAS_WIDTH - 1, CANVAS_HEIGHT - 1, BLACK_COLOR)
  for (let p of pixels) {
    const x = p.coordinate % CANVAS_WIDTH
    const y = (p.coordinate - x) / CANVAS_WIDTH
    img.drawPoint(x, y, p.color)
  }
  fs.writeFileSync(BG_IMG_BMP, img.getFileData())
  const cmd = format('convert %s %s', BG_IMG_BMP, BG_IMG_PNG)
  shell.exec(cmd)
}

async function updateGameInfo() {
  console.log('=========updateGameInfo==========')
  const pixels = repo.getNewPixels()
  for (let p of pixels) {
    cachePixels.set(p[0], p[1])
  }
  console.log('cache pixels size:', cachePixels.size)
  let newPixels = [...cachePixels.values()].sort((l, r) => l.seq - r.seq)
  // let rankingList = [...allUsers.values()].sort((l, r) => r.pixelsPainted - l.pixelsPainted).slice(0, RANKING_LIMIT)
  const rankingList = await repo.getTopUsers(RANKING_LIMIT)
  const newUsers = repo.getNewUsers()
  const latestLogs = await repo.getLatestLogs(LATEST_LOG_LIMIT)
  if (newPixels.length < LATEST_LOG_LIMIT) {
    newPixels = latestLogs.sort((l, r) => l.seq - r.seq)
  }
  fs.writeFileSync(GAME_INFO_FILE, utils.prettyJson({
    newUsers,
    newPixels,
    rankingList,
    gameInfo,
    gameConfigure,
  }))
  repo.clearNewUsers()
}

async function updateBackground() {
  console.log('=========updateBackground==========')
  const allPixels = await repo.getAllPixels()
  createBackgroundImage(allPixels)
  await updateGameInfo()
  cachePixels = repo.getNewPixels()
  repo.clearNewPixels()
}

async function fetchGameInfo() {
  console.log('fetchGameInfo ...')
  let r = await contract.getGameInfo().call()
  let balance = tronWeb.fromSun(r[0].toString())
  let marketValue = tronWeb.fromSun(r[1].toString())
  let grossIncome = tronWeb.fromSun(r[2].toString())
  let bonusIndex = tronWeb.fromSun(r[3].toString())
  let pixels = r[4]
  let pixelsPainted = Number(r[5].toString())
  let lastPaintedAt = r[6].toString()
  let lastPainter = tronWeb.address.fromHex(r[7].toString())
  let endTime = r[8].toString()
  let lastStageIncome = toTrx(r[9].toString())
  let teamBalance = toTrx(r[10].toString())
  let stage = r[11]
  let potAmount = toTrx(r[12].toString())
  // console.log('get game info:', r)
  r = await tronWeb.trx.getCurrentBlock()
  let currentHeight = r.block_header.raw_data.number

  r = await tronWeb.trx.getAccountResources(config.ownerAddress)
  let accountResources = r
  gameInfo = {
    balance,
    marketValue,
    grossIncome,
    bonusIndex,
    pixels,
    pixelsPainted,
    lastPaintedAt,
    lastPainter,
    endTime,
    stage,
    potAmount,
    lastStageIncome,
    teamBalance,

    currentHeight,
    accountResources,
  }
}

async function fetch() {
  console.log('run fetch ...')
  try {
    await fetchGameInfo()
    await logFetcher.fetch()
    console.log('A total of %d logs were fetched this time', logFetcher.fetchCount)
    if (logFetcher.haveRollback) {
      cachePixels = new Map
    }
    if (logFetcher.haveRollback || Date.now() - lastRefreshTime > REFRESH_INTERVAL_MS) {
      await updateBackground()
      lastRefreshTime = Date.now()
    } else {
      await updateGameInfo()
    }
  } catch (e) {
    console.log('got exception:', e)
  }
}

async function fetchGameConfigure() {
  let results = await Promise.all([
    contract.canvasWidth().call(),
    contract.canvasHeight().call(),
    contract.activatePixelThreshold().call(),
    contract.lastStageIncomeThreshold().call(),
    contract.lastStageDuratio().call(),
    contract.defaultPrice().call(),
    contract.incrementRate().call(),
    contract.bonusRatio().call(),
    contract.potRatio().call(),
    contract.referrerRatio().call(),
    contract.feeRatio().call(),
  ])
  gameConfigure = {
    canvasWidth: results[0],
    canvasHeight: results[1],
    activatePixelThreshold: results[2],
    lastStageIncomeThreshold: toTrx(results[3].toString()),
    lastStageDuratio: results[4],
    defaultPrice: toTrx(results[5].toString()),
    incrementRate: results[6],
    bonusRatio: results[7],
    potRatio: results[8],
    referrerRatio: results[9],
    feeRatio: results[10],
  }
  console.log('gameConfigure:', gameConfigure, results)
}

async function init() {
  console.log('init ...')
  tronWeb = require('./tronweb')
  contract = await tronWeb.contract().at(CONTRACT_ID)
  repo = new Repository({ dir: config.stateDir })
  await repo.init()
  const logOffset = await repo.getLogCount()
  console.log('startup log offset: %d', logOffset)
  logFetcher = new LogFetcher({
    tronWeb,
    contract,
    repo,
    fetchLimit: DEFAULT_FETCH_LIMIT,
    offset: logOffset
  })
}

function calcHandover(price) {
  let i = 0
  const dp = gameConfigure.defaultPrice * 1000000
  const rate = (100 + gameConfigure.incrementRate) / 100
  while (price - dp * Math.pow(rate, i) > dp * 0.001) {
    i++
    if (i > 99) {
      console.log('This is unexpected!!!!!!', dp, rate, price, i)
      break
    }
  }
  return i
}

function startServer() {
  const app = new Koa()
  const router = new Router()

  router.get('/admin/stats', async (ctx) => {
    const count = await repo.getUserCount()
    ctx.body = {
      gameInfo,
      users: count,
    }
  })

  router.get('/api/pixels', async (ctx) => {
    const owner = ctx.query.owner
    if (!owner) {
      ctx.status = 400
      return
    }
    const offset = Number(ctx.query.offset) || 0
    const limit = Number(ctx.query.limit) || 20
    const count = await repo.getOwnedCount(owner)
    const pixels = await repo.getOwnedPixels(owner, offset, limit)
    ctx.body = { count, pixels }
  })

  router.get('/api/pixel/:coordinate', async (ctx) => {
    let coordinate = Number(ctx.params.coordinate)
    const pixel = await repo.getPixel(coordinate)
    if (!pixel) {
      ctx.body = {
        price: gameConfigure.defaultPrice * 1000000,
        coordinate,
        handover: 0,
        color: 0,
      }
      return
    }
    pixel.handover = calcHandover(pixel.price) +1
    pixel.price = pixel.price * (100 + gameConfigure.incrementRate) / 100
    ctx.body = pixel
  })

  app
    .use(router.routes())
    .use(router.allowedMethods())
  app.listen(config.server.port)
  console.log('server started on: %d', config.server.port)
}

async function main() {
  await init()
  await fetchGameConfigure()
  await fetchGameInfo()
  await logFetcher.fetch()
  repo.clearNewPixels()
  repo.clearNewUsers()
  await updateBackground()

  startServer()
  utils.loopAsync(fetch, 3000)
}

main().then().catch(console.log)
